﻿using System;
using System.Collections.Generic;
using Microsoft.VisualStudio.SourceSafe.Interop;
using System.IO;

namespace VSSLib
{
    public delegate void GetProjectOrFileHandler(object sender, String projectOrFile);

    public class SourceSafe : IDisposable
    {
        public event GetProjectOrFileHandler OnGetProjectOrFile;

        private VSSDatabase _vssDatabase;

        public void OpenDatabase(String srcSafeIni, String username, String password)
        {
            #region validation
            if (String.IsNullOrEmpty(srcSafeIni))
                throw new ArgumentNullException("srcSafeIni");
            if (String.IsNullOrEmpty(username))
                throw new ArgumentNullException("username");
            #endregion

            try
            {
                if (_vssDatabase != null)
                    _vssDatabase.Close();
            }
            catch (Exception) { }

            try
            {

                _vssDatabase = new VSSDatabase();
                _vssDatabase.Open(srcSafeIni, username, password);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error opening database.", ex);
            }
        }

        public void CreateProject(String projectPath, String newSubProjectName)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");
            if (String.IsNullOrEmpty(newSubProjectName))
                throw new ArgumentNullException("newSubProjectName");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                item.NewSubproject(newSubProjectName, null);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error creating new project.", ex);
            }
        }

        /// <summary>
        /// dodaje plik do repozytorium
        /// </summary>
        /// <param name="projectPath">sciezka do projektu vss</param>
        /// <param name="filePath">lokalna pelna sciezka do pliku na dysku</param>
        public void AddFile(String projectPath, String filePath)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");
            if (String.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                item.Add(filePath, null, 0);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error adding file.", ex);
            }
        }

        /// <summary>
        /// jesli plik jest wycheckooutowany przez dowolnego usera (rowniez mnie) to zwraca true
        /// </summary>
        /// <param name="filePath"></param>
        /// <returns></returns>
        public bool IsCheckedOut(String filePath, out String user)
        {
            if (String.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            user = null;
            try
            {
                var item = _vssDatabase.get_VSSItem(filePath, false);
                if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    throw new SourceSafeException(filePath + " is a project.");

                var checkOuts = item.Checkouts;
                if (checkOuts != null)
                {
                    if (checkOuts.Count != 0)
                        user = checkOuts[1].Username;

                    return checkOuts.Count != 0;
                }

                throw new SourceSafeException("Error getting status file " + filePath);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error checking file status " + filePath, ex);
            }
        }

        public void CheckOutFile(String filePath)
        {
            if (String.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            try
            {

                VSSItem item = _vssDatabase.get_VSSItem(filePath, false);


                if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    throw new SourceSafeException("You are trying to checkout a project " + filePath);

                if (item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_CHECKEDOUT || item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_CHECKEDOUT_ME)
                    throw new SourceSafeException("File is alreadey checked out " + filePath);

                item.Checkout(null, null, (int)VSSFlags.VSSFLAG_CHKEXCLUSIVEYES + (int)VSSFlags.VSSFLAG_GETYES + (int)VSSFlags.VSSFLAG_REPREPLACE);

            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error checking out file " + filePath, ex);
            }
        }

        public void CheckIn(String filePath)
        {
            if (filePath == null)
                throw new ArgumentNullException("filePath");
            if (filePath == String.Empty)
                throw new ArgumentNullException("filePath");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(filePath, false);
                if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    throw new SourceSafeException("You are trying to undo checkin a project " + filePath);

                if (item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_NOTCHECKEDOUT)
                    throw new SourceSafeException("File is not checkouted " + filePath);

                if (item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_CHECKEDOUT)
                    throw new SourceSafeException("File is not checkouted to user " + _vssDatabase.Username);

                #region sprawdzic czy plik wycheckoutowany zostal na tym komputerze i czy sciezka wyczeckoutowania istnieje na dysku
                //zakladamy model repozytorium z blokowaniem, czyli tylko jeden user moze wyczeckoutowac plik
                if (item.Checkouts[1].Username != _vssDatabase.Username)
                    throw new SourceSafeException("File is checkouted to " + item.Checkouts[1].Username);

                if (item.Checkouts[1].Machine != Environment.MachineName)
                    throw new SourceSafeException("File is checkouted on machine " + item.Checkouts[1].Machine);

                if (!File.Exists(item.Checkouts[1].LocalSpec + Path.DirectorySeparatorChar + filePath.Substring(filePath.LastIndexOf(@"/") + 1)))
                    throw new SourceSafeException("File doesnt exists " + item.Checkouts[1].LocalSpec + Path.DirectorySeparatorChar + filePath.Substring(filePath.LastIndexOf(@"/") + 1));

                #endregion

                item.Checkin(null, null, 0);


            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Undocheckout error on file " + filePath, ex);
            }
        }

        public void UndoCheckOut(String filePath)
        {
            if (filePath == null)
                throw new ArgumentNullException("filePath");
            if (filePath == String.Empty)
                throw new ArgumentNullException("filePath");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(filePath, false);
                if (item.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    throw new SourceSafeException("You are trying to undo checkout a project " + filePath);

                if (item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_NOTCHECKEDOUT)
                    throw new SourceSafeException("File is not checkouted " + filePath);

                if (item.IsCheckedOut == (int)VSSFileStatus.VSSFILE_CHECKEDOUT)
                    throw new SourceSafeException("File is not checkouted to user " + _vssDatabase.Username);

                #region sprawdzic czy plik wycheckoutowany zostal na tym komputerze i czy sciezka wyczeckoutowania istnieje na dysku
                //zakladamy model repozytorium z blokowaniem, czyli tylko jeden user moze wyczeckoutowac plik
                if (item.Checkouts[1].Username != _vssDatabase.Username)
                    throw new SourceSafeException("File is checkouted to " + item.Checkouts[1].Username);

                if (item.Checkouts[1].Machine != Environment.MachineName)
                    throw new SourceSafeException("File is checkouted on machine " + item.Checkouts[1].Machine);

                if (!File.Exists(item.Checkouts[1].LocalSpec + Path.DirectorySeparatorChar + filePath.Substring(filePath.LastIndexOf(@"/") + 1)))
                    throw new SourceSafeException("File doesnt exists " + item.Checkouts[1].LocalSpec + Path.DirectorySeparatorChar + filePath.Substring(filePath.LastIndexOf(@"/") + 1));

                #endregion

                item.UndoCheckout(null, (int)VSSFlags.VSSFLAG_REPREPLACE);


            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Undocheckout error on file " + filePath, ex);
            }
        }

        public void GetLatestVersion(String projectPath, bool recursive, bool getOnlyDifferentFiles)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(projectPath, false);

                if (item.Type == (int)VSSItemType.VSSITEM_FILE)
                {

                    

                    //FileInfo fileInfo = new FileInfo(item.LocalSpec);
                    //if ((fileInfo.Attributes & FileAttributes.ReadOnly) != FileAttributes.ReadOnly)
                    //    fileInfo.Attributes = fileInfo.Attributes | FileAttributes.ReadOnly;

                    String file = null;
                    if (recursive)
                    {
                        if (getOnlyDifferentFiles)
                        {
                            if (!File.Exists(item.LocalSpec) ||
                                (File.Exists(item.LocalSpec) && item.get_IsDifferent(null)))
                            {
                                DeleteFileIfExists(item.LocalSpec);


                                if (OnGetProjectOrFile != null)
                                    OnGetProjectOrFile(this, item.Spec);

                                item.Get(ref file, (int)(VSSFlags.VSSFLAG_RECURSYES | VSSFlags.VSSFLAG_REPREPLACE));
                            }
                        }
                        else
                        {
                            DeleteFileIfExists(item.LocalSpec);


                            if (OnGetProjectOrFile != null)
                                OnGetProjectOrFile(this, item.Spec);
                            item.Get(ref file, (int)(VSSFlags.VSSFLAG_RECURSYES | VSSFlags.VSSFLAG_REPREPLACE));
                        }
                    }
                    else
                    {
                        if (getOnlyDifferentFiles)
                        {
                            if (!File.Exists(item.LocalSpec) ||
                                (File.Exists(item.LocalSpec) && item.get_IsDifferent(null)))
                            {
                                DeleteFileIfExists(item.LocalSpec);

                                if (OnGetProjectOrFile != null)
                                    OnGetProjectOrFile(this, item.Spec);

                                item.Get(ref file, (int)(VSSFlags.VSSFLAG_RECURSNO | VSSFlags.VSSFLAG_REPREPLACE));
                            }
                        }
                        else
                        {
                            DeleteFileIfExists(item.LocalSpec);


                            if (OnGetProjectOrFile != null)
                                OnGetProjectOrFile(this, item.Spec);
                            item.Get(ref file, (int)(VSSFlags.VSSFLAG_RECURSNO | VSSFlags.VSSFLAG_REPREPLACE | VSSFlags.VSSFLAG_GETYES));
                        }
                    }
                }
                else
                    GetProject(item, recursive, getOnlyDifferentFiles);


            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting latest version.", ex);
            }
        }

        public void GetLatestVersion(String projectPath, bool recursive)
        {
            GetLatestVersion(projectPath, recursive, false);
        }

        private void GetProject(IVSSItem folderItem, bool recursive, bool getOnlyDifferentFiles)
        {
            if (!Directory.Exists(folderItem.LocalSpec))
                Directory.CreateDirectory(folderItem.LocalSpec);

            IVSSItems items = folderItem.get_Items(false);


            foreach (IVSSItem item in items)
            {

                if (item.Type == (int)VSSItemType.VSSITEM_FILE)
                {
                    String file = null;


                    if (recursive)
                    {
                        if (getOnlyDifferentFiles)
                        {
                            if (!File.Exists(item.LocalSpec) ||
                                (File.Exists(item.LocalSpec) && item.get_IsDifferent(null)))
                            {
                                DeleteFileIfExists(item.LocalSpec);


                                if (OnGetProjectOrFile != null)
                                    OnGetProjectOrFile(this, item.Spec);
                                item.Get(ref file, (int)VSSFlags.VSSFLAG_RECURSYES);
                            }
                        }
                        else
                        {
                            DeleteFileIfExists(item.LocalSpec);

                            if (OnGetProjectOrFile != null)
                                OnGetProjectOrFile(this, item.Spec);
                            item.Get(ref file, (int)VSSFlags.VSSFLAG_RECURSYES);
                        }

                    }
                    else
                    {
                        if (getOnlyDifferentFiles)
                        {
                            if (!File.Exists(item.LocalSpec) ||
                                (File.Exists(item.LocalSpec) && item.get_IsDifferent(null)))
                            {
                                DeleteFileIfExists(item.LocalSpec);

                                if (OnGetProjectOrFile != null)
                                    OnGetProjectOrFile(this, item.Spec);
                                item.Get(ref file, (int)VSSFlags.VSSFLAG_RECURSNO);
                            }
                        }
                        else
                        {
                            DeleteFileIfExists(item.LocalSpec);

                            if (OnGetProjectOrFile != null)
                                OnGetProjectOrFile(this, item.Spec);
                            item.Get(ref file, (int)VSSFlags.VSSFLAG_RECURSNO);
                        }

                    }
                }
                else
                {
                    if (recursive)
                        GetProject(item, recursive, getOnlyDifferentFiles);
                }
            }
        }

        private void DeleteFileIfExists(String localSpec)
        {
            if (!File.Exists(localSpec))
                return;

            FileInfo fileInfo = new FileInfo(localSpec);
            if ((fileInfo.Attributes & FileAttributes.ReadOnly) == FileAttributes.ReadOnly)
            {
                fileInfo.Attributes = fileInfo.Attributes & ~FileAttributes.ReadOnly;
            }

            File.Delete(localSpec);
        }

        public void Delete(String filePath)
        {
            if (String.IsNullOrEmpty(filePath))
                throw new ArgumentNullException("filePath");

            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(filePath, false);
                item.Deleted = true;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error deleting file.", ex);
            }
        }

        public void Destroy(String fileOrProjectPath)
        {
            if (String.IsNullOrEmpty(fileOrProjectPath))
                throw new ArgumentNullException("fileOrProjectPath");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(fileOrProjectPath, false);
                item.Destroy();
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error destroying " + fileOrProjectPath, ex);
            }
        }

        public List<KeyValuePair<String, bool>> GetChilds(String projectPath)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");

            List<KeyValuePair<String, bool>> lista = new List<KeyValuePair<string, bool>>();
            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                IVSSItems childs = item.get_Items(false);

                foreach (IVSSItem child in childs)
                {
                    if (child.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    {
                        lista.Add(new KeyValuePair<string, bool>(child.Spec, true));
                    }
                    else
                        lista.Add(new KeyValuePair<string, bool>(child.Spec, false));
                }

                return lista;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting childs for project " + projectPath, ex);
            }
        }

        public List<String> GetSubProjects(String projectPath)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");

            var subProjects = new List<string>();
            try
            {
                VSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                IVSSItems items = item.get_Items(false);
                foreach (VSSItem vssItem in items)
                {
                    if (vssItem.Type == (int)VSSItemType.VSSITEM_PROJECT)
                    {
                        subProjects.Add(vssItem.Name);
                    }
                }

                return subProjects;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting project items.", ex);
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="projectOrFilePath"></param>
        /// <param name="newName"></param>
        public void Rename(String projectOrFilePath, String newName)
        {
            if (String.IsNullOrEmpty(projectOrFilePath))
                throw new ArgumentNullException("projectOrFilePath");
            if (String.IsNullOrEmpty(newName))
                throw new ArgumentNullException("newName");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectOrFilePath, false);
                item.Name = newName;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error renaming item " + projectOrFilePath, ex);
            }
        }

        public void Share(String projectPath, String fileOrProjectToBeShared, bool recursive)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");
            if (String.IsNullOrEmpty(fileOrProjectToBeShared))
                throw new ArgumentNullException("fileOrProjectToBeShared");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                VSSItem itemToBeShared = _vssDatabase.get_VSSItem(fileOrProjectToBeShared, false);

                item.Share(itemToBeShared, null, recursive ? (int)VSSFlags.VSSFLAG_RECURSYES : (int)VSSFlags.VSSFLAG_RECURSNO);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException(string.Format("Error sharing item {0}", fileOrProjectToBeShared), ex);
            }
        }

        public String GetWorkingFolder(String projectPath)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                return item.LocalSpec;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting working folder for project " + projectPath, ex);
            }
        }

        public void SetWorkingFolder(String projectPath, String workingFolder)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                item.LocalSpec = workingFolder;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error setting working folder for project " + projectPath, ex);
            }
        }

        public void Label(String fileOrProjectPath, String label)
        {
            if (String.IsNullOrEmpty(fileOrProjectPath))
                throw new ArgumentNullException("fileOrProjectPath");

            if (String.IsNullOrEmpty(label))
                throw new ArgumentNullException("label");

            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(fileOrProjectPath, false);
                item.Label(label, null);
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error setting label for item " + fileOrProjectPath, ex);
            }
        }

        public List<VSSVersion> GetVersions(String fileOrProjectPath)
        {
            if (String.IsNullOrEmpty(fileOrProjectPath))
                throw new ArgumentNullException("fileOrProjectPath");

            List<VSSVersion> versions = new List<VSSVersion>();
            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(fileOrProjectPath, false);
                IVSSVersions vssVersions = item.get_Versions(0);
                foreach (IVSSVersion vssVersion in vssVersions)
                {
                    
                    versions.Add(new VSSVersion(vssVersion.VersionNumber, vssVersion.Comment, vssVersion.Date, vssVersion.Username, vssVersion.Action));
                }
                return versions;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting versions for item " + fileOrProjectPath, ex);
            }
        }


        public String GetParent(String projectPath)
        {
            if (String.IsNullOrEmpty(projectPath))
                throw new ArgumentNullException("projectPath");
            try
            {
                IVSSItem item = _vssDatabase.get_VSSItem(projectPath, false);
                return item.Parent.Spec;
            }
            catch (Exception ex)
            {
                throw new SourceSafeException("Error getting parent project for " + projectPath, ex);
            }
        }


        #region IDisposable Members

        public void Dispose()
        {
            try
            {
                if (_vssDatabase != null)
                    _vssDatabase.Close();
            }
            catch (Exception)
            { }
        }

        #endregion
    }
}
